/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       cloth.cpp
 * Author:     karooolek
 * Created on: 2009-09-29
 *
 **********************************************************************************************************************/

#include "cloth.h"
#include "nxustreaming.h"

namespace mGameEngine
{
namespace Physics
{

Cloth::Cloth() :
    Actor(),
    _attachedBodies(),
    _vertices(), _normals(), _indices(), _vertNumber(0), _indNumber(0),
    _density(1.0f), _bending(0.5f), _stretching(0.5f),
    _cloth(NULL), _jointActors()
{
}


Cloth::Cloth(const Vector<Vector3> &vertices, const Vector<Vector3> &normals, const Vector<uint16> &indices,
             float density, float bending, float stretching) :
    Actor(),
    _attachedBodies(),
    _vertices(vertices), _normals(normals), _indices(indices), _vertNumber(vertices.size()), _indNumber(indices.size()),
    _density(density), _bending(bending), _stretching(stretching),
    _cloth(NULL), _jointActors()
{
}

Cloth::~Cloth()
{
    // delete cloth
    if(_cloth)
    {
        _cloth->getScene().releaseCloth(*_cloth);
    }

    // release actors
    while(!_jointActors.empty())
    {
        NxActor *actor = _jointActors.back();
        actor->getScene().releaseActor(*actor);
        _jointActors.pop_back();
    }
}

NxActor *Cloth::_createJointActor(Joint *joint) const
{
    if(!_cloth)
    {
        return NULL;
    }

    // get anchor
    const Matrix4x4 &anchor = joint->getFirstActor() == this ?
                              joint->getFirstActorAnchor() :
                              joint->getSecondActorAnchor();
    
    // some actors already created, check for overlapping and reuse
    for(List<NxActor *>::ConstIterator i = _jointActors.begin(); i != _jointActors.end(); ++i)
    {
        NxActor *actor = *i;
        const NxVec3 &p = actor->getCMassGlobalPosition();
        if(p.distanceSquared(NxVec3(anchor.m[3])) < 0.01f)
        {
            return actor;
        }
    }

    // get scene
    NxScene *scene = &_cloth->getScene();

    // create material
    NxMaterialDesc materialDesc;
    NxMaterial *material = scene->createMaterial(materialDesc);

    // create body descriptor
    NxBodyDesc bodyDesc;
    bodyDesc.mass = 0.01f;
    bodyDesc.flags |= NX_BF_DISABLE_GRAVITY;

    // create actor descriptor
    NxActorDesc desc;
    desc.body = &bodyDesc;
    NxSphereShapeDesc sphereDesc;
    sphereDesc.localPose.setColumnMajor44(anchor._m);
    sphereDesc.radius = 0.1f;
    desc.shapes.pushBack(&sphereDesc);
    
    // create actor
    NxActor *actor = scene->createActor(desc);
    _jointActors.push_back(actor);
    
    // attach to actor
    NxShape *shape = actor->getShapes()[0];
    _cloth->attachToShape(shape, NX_CLOTH_ATTACHMENT_TWOWAY);

    return actor;
}

void Cloth::setDensity(float density)
{
    if(density <= 0.0f)
    {
        return;
    }

    _density = density;

    if(_cloth)
    {
//        _cloth->set
    }
}

void Cloth::setBending(float bending)
{
    if(bending < 0.0f || bending > 1.0f)
    {
        return;
    }

    _bending = bending;

    if(_cloth)
    {
        _cloth->setBendingStiffness(1.0f - _bending);
    }
}

void Cloth::setStretching(float stretching)
{
    if(stretching <= 0.0f || stretching > 1.0f)
    {
        return;
    }

    _stretching = stretching;

    if(_cloth)
    {
        _cloth->setStretchingStiffness(1.0f - _stretching);
    }
}

void Cloth::attachToBody(Body *body)
{
    if(!body)
    {
        return;
    }

    // cloth already attached to that body
    for(List<Body *>::ConstIterator i = _attachedBodies.begin(); i != _attachedBodies.end(); ++i)
    {
        Body *b = *i;

        if(body == b)
        {
            return;
        }
    }

    _attachedBodies.push_back(body);

    if(_cloth && body->isCreated())
    {
        uint nb = body->_actor->getNbShapes();
        NxShape * const*shapes = body->_actor->getShapes();
        
        for(int i = 0; i != nb; ++i)
        {
            _cloth->attachToShape(shapes[i], NX_CLOTH_ATTACHMENT_TWOWAY);
        }
    }
}

void Cloth::putForce(const Vector3 &force, const Vector3 &radius)
{
    if(_cloth)
    {
        _cloth->addDirectedForceAtPos(NxVec3(getTransformation() * radius), NxVec3(force), 1.0f, NX_FORCE);
    }
}

void Cloth::putImpulse(const Vector3 & impulse, const Vector3 & radius)
{
    if(_cloth)
    {
        _cloth->addDirectedForceAtPos(NxVec3(getTransformation() * radius), NxVec3(impulse), 1.0f, NX_IMPULSE);
    }
}

void Cloth::putAcceleration(const Vector3 &acc, const Vector3 &radius)
{
    if(_cloth)
    {
        _cloth->addDirectedForceAtPos(NxVec3(getTransformation() * radius), NxVec3(acc), 1.0f, NX_ACCELERATION);
    }
}

void Cloth::putAcceleration(const Vector3 &acc)
{
    if(_cloth)
    {
        _cloth->setWindAcceleration(NxVec3(acc));
    }
}

void Cloth::create(NxScene *scene) const
{
    // already created
    if(_created)
    {
        return;
    }

    // create mesh
    NxClothMeshDesc meshDesc;
    meshDesc.numVertices = _vertices.size();
    meshDesc.numTriangles = _indices.size() / 3;
    meshDesc.points = (void *)(&_vertices[0]);
    meshDesc.pointStrideBytes = sizeof(Vector3);
    meshDesc.triangles = (void *)(&_indices[0]);
    meshDesc.triangleStrideBytes = 3 * sizeof(uint16);
    meshDesc.flags |= NX_MF_16_BIT_INDICES;
    
    NxCookingInterface *cooking = NxGetCookingLib(NX_PHYSICS_SDK_VERSION);
    cooking->NxInitCooking();

    NxuMemoryWriteBuffer buf;
    cooking->NxCookClothMesh(meshDesc, buf);

    NxuMemoryReadBuffer buf2(buf.data);
    NxClothMesh *mesh = scene->getPhysicsSDK().createClothMesh(buf2);

    // define receive buffers
    NxMeshData recvBuf;
    recvBuf.verticesPosBegin = (void*)(&_vertices[0]);
    recvBuf.verticesPosByteStride = sizeof(Vector3);
    recvBuf.verticesNormalBegin = (void *)(&_normals[0]);
    recvBuf.verticesNormalByteStride = sizeof(Vector3);
    recvBuf.numVerticesPtr = (NxU32 *)&_vertNumber;
    recvBuf.maxVertices = _vertices.size();
    recvBuf.indicesBegin = (void *)(&_indices[0]);
    recvBuf.indicesByteStride = sizeof(uint16);
    recvBuf.numIndicesPtr = (NxU32 *)&_indNumber;
    recvBuf.maxIndices = _indices.size();
    recvBuf.flags |= NX_MF_16_BIT_INDICES;
    
    // create cloth
    NxClothDesc desc;
    desc.clothMesh = mesh;
    desc.meshData = recvBuf;
    desc.density = _density;
    desc.bendingStiffness = 1.0f - _bending;
    desc.stretchingStiffness = 1.0f - _stretching;
    desc.thickness = 0.1f;
    desc.attachmentResponseCoefficient = 1.0f;
    desc.flags |= NX_CLF_GRAVITY;
    _cloth = scene->createCloth(desc);

    // cloth is created
    _created = true;

    // attach to bodies
    for(List<Body *>::ConstIterator i = _attachedBodies.begin(); i != _attachedBodies.end(); ++i)
    {
        Body *body = *i;

        if(!body->isCreated())
        {
            body->create(scene);
        }

        uint nb = body->_actor->getNbShapes();
        NxShape* const *shapes = body->_actor->getShapes();
        
        for(int i = 0; i != nb; ++i)
        {
            _cloth->attachToShape(shapes[i], NX_CLOTH_ATTACHMENT_TWOWAY);
        }
    }

    // delete mesh
    cooking->NxCloseCooking();
}

Vector3 Cloth::getPointVelocity(const Vector3 &p)
{
    Vector3 v;
    if(_cloth)
    {
        // TODO
    }
    return v;
}

void Cloth::updateExternalVertices(Vector3 *positions, uint8 positionsStride,
                                   Vector3 *normals, uint8 normalsStride) const
{
    uint verts = _vertices.size();
    for(uint i = 0; i != verts; ++i)
    {
        *(Vector3 *)((uint8 *)positions + i*positionsStride) = _vertices[i];
        *(Vector3 *)((uint8 *)normals + i*normalsStride) = _normals[i];
    }
}

}
}

